package fbp.engine;


import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;


/** A Packet may either contain an Object, when <code> type</type> is <code> NORMAL </code>,
 * or a String, when <code>type</code> is not <code>NORMAL</code>.  The latter case
 * is used for things like open and close brackets (where the String will be the name
 * of a group. e.g. accounts)
*/

public class Packet implements Serializable {

  /* *
     * Copyright 2007, 2012, J. Paul Morrison.  At your option, you may copy, 
     * distribute, or make derivative works under the terms of the Clarified Artistic License, 
     * based on the Everything Development Company's Artistic License.  A document describing 
     * this License may be found at http://www.jpaulmorrison.com/fbp/artistic2.htm. 
     * THERE IS NO WARRANTY; USE THIS PRODUCT AT YOUR OWN RISK.
     * */
  public static final int OPEN = 1;

  public static final int CLOSE = 2;

  public static final int NORMAL = 0;

  Object content;

  int type;

  Object owner;

  HashMap<String, Chain> chains = null;

  HashMap<String, Object> attrs = null;

  // An iteration that has nothing to iterate.               
  static Iterator nullIter = new HashMap().values().iterator();

  public Packet(final int newType, final String newName, final Thread newOwner) {
    content = newName;
    setOwner(newOwner);
    type = newType;
  }

  public Packet(final Object newContent, final Thread newOwner) {
    content = newContent;
    setOwner(newOwner);
    type = NORMAL;
  }

  /** Maintains a Chain of Packets attached to this Packet.
  * A Packet may have multiple named Chains attached to it, accessed via a HashMap.
  * Since Packets are attached to Chains, as well as Chains to Packets,
  * this results in an alternation of Packets and Chains, creating a tree structure.
  */

  public void attach(final String name, final Packet subordinate) {
    if (subordinate == null) {
      FlowError.complain("Null packet reference in 'attach' method call: " + Thread.currentThread().getName());
    }
    if (chains == null) {
      chains = new HashMap<String, Chain>();
    }
    Chain chain = chains.get(name);
    if (chain == null) {
      chain = new Chain(name, this);
      chains.put(name, chain);
    }
    Packet root = getRoot();
    if (subordinate.owner != root.owner) {
      FlowError.complain("Attaching unowned packet");
    }
    if (subordinate == root) {
      FlowError.complain("Loop in packet chain");
    }
    subordinate.setOwner(chain);
    chain.members.add(subordinate);
  }

  /** Clear the owner of a Packet, and reduce the number of Packets owned by the owner
  *  (if owner is a Component) - if Packet is chained, owner is Chain
  */

  void clearOwner() {
    if (owner instanceof Component) {
      Component c = (Component) owner;
      c.packetCount--;

    }
    owner = null;
  }

  /** Detach Packet from named chain
  */

  public boolean detach(final String name, final Packet subordinate) {
    if (subordinate == null) {
      FlowError.complain("Null packet reference in 'detach' method call: " + Thread.currentThread().getName());
    }
    if (chains == null) {
      return false;
    }
    Chain chain = chains.get(name);
    if (chain == null) {
      return false;
    }
    if (!chain.members.remove(subordinate)) {
      return false;
    }
    Packet root = getRoot();
    subordinate.setOwner(root.owner);
    return true;
  }

  /** Get named attribute of Packet - may be any Object
  */
  public Object getAttribute(final String key) {
    if (attrs != null) {
      return attrs.get(key);
    }

    return null;
  }

  /** Get all attributes of this Packet (as Iterator)
  */
  public Iterator getAttributes() {
    if (attrs != null) {
      return attrs.keySet().iterator();
    }

    return nullIter;
  }

  /** Get named chain (as Iterator)
  */

  public Iterator getChain(final String name) {
    if (chains == null) {
      return nullIter;
    }
    Chain chain = chains.get(name);
    if (chain != null) {
      return chain.members.iterator();
    }

    return nullIter;
  }

  /** Get all chains for this Packet (as Iterator)
  */

  public Iterator getChains() {
    if (chains != null) {
      return chains.keySet().iterator();
    }

    return nullIter;
  }

  /** Get contents of this Packet - may be any Object
  */

  public Object getContent() {
    //if (type == NORMAL)
    return content;
    // else
    // return null;
  }

  public String getName() {
    if (type == NORMAL) {
      return null;
    }

    return (String) content;
  }

  /** Get root of this Packet - it follows the Packet owner chain up until
  * it finds a Packet that is owned by a Component rather than by a Chain
  */

  Packet getRoot() {
    Packet p = this;
    while (p.owner instanceof Chain) {
      p = ((Chain) p.owner).head;
    }
    return p;
  }

  /** This method returns the type of a Packet
  */

  public int getType() {
    return type;
  }

  /** Make an Object a named attribute of a Packet
  */

  public void putAttribute(final String key, final Object value) {

    if (attrs == null) {
      attrs = new HashMap<String, Object>();
    }
    attrs.put(key, value);
  }

  /** Remove a named attribute from a Packet (does not return the attribute)
  */

  public void removeAttribute(final String key) {
    if (attrs != null) {
      attrs.remove(key);
    }
  }

  /** Change the owner of a Packet - if the owner is a Component,
  * increment the number of Packets owned by that Component
  * (when the Component is deactivated, it must no longer own any Packets)
  */

  void setOwner(final Object newOwner) {
    clearOwner();
    owner = newOwner;
    if (owner instanceof Component) {
      Component c = (Component) owner;
      c.packetCount++; // count of owned packets

    }
  }

  @Override
  public String toString() {
    String value = "null";
    final String names[] = { "NORMAL", "OPEN", "CLOSE" };
    if (getType() == NORMAL) {
      Object obj = getContent();
      if (obj != null) {
        value = obj.toString();
      }
    } else {
      value = names[getType()];
      value += "; " + getName();
    }
    return String.format("%1$s", value);
  }
}
